import arrow
import hashlib
import requests
import threading
import time
import PySimpleGUI as sg
from collections import deque
from itertools import chain, repeat

sg.theme("Default1")

requests.packages.urllib3.disable_warnings()


header_string = """
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,und;q=0.6
Connection: keep-alive
Content-Type: application/json;charset=UTF-8
Host: lab.whuh.com
Origin: https://lab.whuh.com
Referer: https://lab.whuh.com/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
sec-ch-ua: ".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
"""


def parse_header_string(header_string):
    table = {}
    for line in header_string.strip().split("\n"):
        k, v = line.split(":", 1)
        k = k.strip()
        v = v.strip()
        table[k] = v
    return table


USER_HEADERS = {}
USER_DATA = {}
SESSION = None

DEFAULT_USERNAME = ""
DEFAULT_PASSWORD = ""


def login(username=DEFAULT_USERNAME, password=DEFAULT_PASSWORD):
    global SESSION
    global USER_HEADERS
    global USER_DATA
    SESSION = requests.Session()
    USER_HEADERS = parse_header_string(header_string)
    hashed_password = hashlib.md5(password.encode('utf-8')).hexdigest()
    url = "https://lab.whuh.com/device-appointment-server-api/api/v1/login/login"
    res = SESSION.post(url, json={'username': username, 'password': hashed_password}, headers=USER_HEADERS, verify=False)
    if res.status_code == 200 and res.json()['status'] == 200:
        res_data = res.json()
        USER_HEADERS['Authorization'] = f"Bearer {res_data['data']['token']}"
        USER_DATA = res_data['data']
        return True
    else:
        return False


def logout():
    url = "https://lab.whuh.com/device-appointment-server-api/api/v1/login/logout"
    SESSION.post(url, headers=USER_HEADERS)


def get_datetime_from_server():
    url = "https://lab.whuh.com/device-appointment-server-api/api/v1/login"
    res = SESSION.get(url, headers=USER_HEADERS)
    res_data = res.json()
    return arrow.get(res_data['date'])


def get_equipment_list(order_date='2022-08-01'):
    url = "https://lab.whuh.com/device-appointment-server-api/api/v1/equipment"
    res = SESSION.get(url, params={'pageNum': 1, 'pageSize': 20, 'date': order_date}, headers=USER_HEADERS)
    return res.json()


def get_equipment_details(equipment_id, order_date):
    url = "https://lab.whuh.com/device-appointment-server-api/api/v1/login"
    SESSION.get(url, headers=USER_HEADERS)
    url = f"https://lab.whuh.com/device-appointment-server-api/api/v1/equipment/{equipment_id}/{order_date}"
    res = SESSION.get(url, headers=USER_HEADERS)
    return res.json()


def order_equipment(equipment_id, equipment_name, order_date, order_time, phone):
    url = "https://lab.whuh.com/device-appointment-server-api/api/v1/order/record"
    res = SESSION.post(
        url,
        json={
            'equipmentId': equipment_id,
            'equipmentName': equipment_name,
            'orderDate': order_date,
            'orderTime': order_time,
            'phone': phone,
        },
        headers=USER_HEADERS
    )
    return res.json()



def windowed(seq, n, fillvalue=None, step=1):
    if n < 0:
        raise ValueError('n must be >= 0')
    if n == 0:
        yield tuple()
        return
    if step < 1:
        raise ValueError('step must be >= 1')

    window = deque(maxlen=n)
    i = n
    for _ in map(window.append, seq):
        i -= 1
        if not i:
            i = step
            yield tuple(window)

    size = len(window)
    if size < n:
        yield tuple(chain(window, repeat(fillvalue, n - size)))
    elif 0 < i < min(step, n):
        window += (fillvalue,) * i
        yield tuple(window)


def show_login_window():
    login_layout = [
        [sg.Text("账号", size=(6, 1)),   sg.Input(DEFAULT_USERNAME, size=(20, 1), key="__USERNAME__")],
        [sg.Text("密码", size=(6, 1)),   sg.Input(DEFAULT_PASSWORD, size=(20, 1), key="__PASSWORD__", password_char='*')],
        [sg.Text("", size=(6, 1)), sg.Button("登录", size=(10, 1))]
    ]

    window = sg.Window('登录', login_layout, font=("Any", 11)).finalize()

    while True:
        event, values = window.read()
        if event == sg.WIN_CLOSED or event == 'Cancel': # if user closes window or clicks cancel
            break

        if event == "登录":
            username = values["__USERNAME__"].strip()
            password = values["__PASSWORD__"].strip()

            if login(username=username, password=password):
                show_appointment_date_window()
                break

    window.close()


def show_appointment_date_window():
    now = get_datetime_from_server()
    available_date_list = list(now.shift(days=i).format('YYYY-MM-DD') for i in range(7))
    layout = [
        [sg.Listbox([date for date in available_date_list], size=(20, len(available_date_list)), enable_events=True, key='__DATE_LIST__')],
        [sg.Button("确定"), sg.Button("退出", key="__QUIT__")]
    ]

    window = sg.Window('请选择预日期', layout)

    while True:
        event, values = window.read()
        if event == sg.WIN_CLOSED or event == '__QUIT__':
            break
        if event == "确定":
            print(values["__DATE_LIST__"])
            order_date = values["__DATE_LIST__"][0]
            show_device_list_window(order_date)

    window.close()


def show_device_list_window(order_date):
    device_list = get_equipment_list(order_date)['data']['records']
    layout = [
        [sg.Listbox([f"{i+1:02d} -- {d['name']}" for i, d in enumerate(device_list)], size=(50, len(device_list)), enable_events=True, key='__DEVICE_LIST__')],
        [sg.Button("确定"), sg.Button("退出", key="__QUIT__")]
    ]

    window = sg.Window('请选择要预约的仪器', layout)

    while True:
        event, values = window.read()
        if event == sg.WIN_CLOSED or event == '__QUIT__':
            break
        if event == "确定":
            print(values["__DEVICE_LIST__"])
            device_index = int(values["__DEVICE_LIST__"][0].split(" -- ")[0]) - 1
            device_record = device_list[device_index]
            # pprint.pprint(device_information)
            show_application_window(order_date, device_record['id'], device_record['name'])

    window.close()


def show_application_window(order_date, equipment_id, equipment_name):
    details = get_equipment_details(equipment_id=equipment_id, order_date=order_date)
    available_times = details['data']['equipmentUseTimes'][0]['availableOrderTime']
    order_times = set(details['data']['orderTimes'])
    selected_times = []

    now = get_datetime_from_server()
    start_time = now.replace(hour=8, minute=0, second=0)


    layout = [[sg.Text("请选择1个或多个时间段，将按选取的顺序进行预约尝试")], [sg.Text("红色标记时间段已被预约，不可选取")]]

    for time_list in windowed(available_times, n=5, step=5):
        row = []
        for current_time in time_list:
            if current_time:
                row.append(sg.Text(current_time, text_color="black" if current_time not in order_times else "red", background_color="#D0D0D0", key=current_time, enable_events=True, font="Any 8"))
        layout.append(row)

    layout.append([sg.Text("电话号码：", size=(10, 1)), sg.Input("", key="__PHONE__", size=(15, 1))])

    layout.append([
        sg.Button("开始预约"), sg.Button("返回列表")
    ])

    layout.append([sg.Multiline(size=(100,20), key='-ML-', autoscroll=True, reroute_stdout=True, write_only=True, reroute_cprint=True)])

    window = sg.Window("预约", layout, finalize=True)

    while True:
        event, values = window.read()
        if event == sg.WIN_CLOSED or event == '返回列表':
            break

        if ":" in event:
            current_time = event
            if current_time not in order_times:
                if current_time in selected_times:
                    selected_times.remove(current_time)
                    window[event].update(text_color="black", background_color="#D0D0D0")
                else:
                    selected_times.append(current_time)
                    window[event].update(text_color="white", background_color="#228AE6")
                sg.cprint(f"选取顺序：{selected_times}", c="white on blue")

        if event == "开始预约":
            if len(selected_times) == 0:
                sg.cprint("没有选取任何时间段", c="white on red")
            if len(values['__PHONE__'].strip()) <= 3:
                sg.cprint("请输入电话号码", c="white on red")
            else:
                threading.Thread(target=count_down, args=(window, start_time), daemon=True).start()

        if event == "__START_APPLICATION__":
            submit_application(equipment_id, equipment_name, order_date, selected_times, values['__PHONE__'].strip())

    window.close()


def count_down(window, start_time):
    while True:
        now = get_datetime_from_server()
        seconds_remaining = (start_time - now).seconds
        if start_time > now and seconds_remaining > 30:
            time_msg = start_time.humanize(now, locale='zh')
            sg.cprint(f"将在{time_msg}开始预约 (剩余{seconds_remaining}秒)", c="white on blue")
            time.sleep(30)
        elif start_time > now and seconds_remaining > 0:
            time_msg = start_time.humanize(now, locale='zh')
            sg.cprint(f"将在{time_msg}开始预约 (剩余{seconds_remaining}秒)", c="white on blue")
            time.sleep(seconds_remaining)
            window.write_event_value('__START_APPLICATION__', (threading.current_thread().name, ))  
            break
        else:
            sg.cprint(f"立即开始预约", c="white on green")
            window.write_event_value('__START_APPLICATION__', (threading.current_thread().name, ))   
            break


def submit_application(equipment_id, equipment_name, order_date, selected_times, phone):
    marked_times = set()
    while len(selected_times) > 0:
        order_time = selected_times.pop(0)
        if order_time in marked_times:
            sg.cprint(f"{order_time}在预约开放前被预约，放弃该时间段", c="white on blue")
            continue
        retry_times = 0
        while True:
            res = order_equipment(equipment_id, equipment_name, order_date, order_time, phone)
            if res['status'] == 400 and res['message'] == "该设备的开放预约时间是[08:00到22:00]":
                retry_times += 1
                sg.cprint(f"尝试预约，服务器返回预约还未开放: {order_date} {order_time} [尝试{retry_times}次]", c="white on blue")
                details = get_equipment_details(equipment_id=equipment_id, order_date=order_date)
                for current_time in details['data']['orderTimes']:
                    if current_time not in marked_times:
                        sg.cprint(f"检测到时间段在预约开放前被标记为“已预约”状态：{current_time}", c="white on blue")
                        marked_times.add(current_time)
                if order_time in marked_times:
                    sg.cprint(f"{order_time}在预约开放前被预约，放弃该时间段", c="white on blue")
                    break
                else:
                    time.sleep(1)
            else:
                break

        if res['status'] == 200:
            sg.cprint(f"预约成功: {order_date} {order_time}", c="white on green")
            break
        else:
            sg.cprint(f"{res['message']}: {order_date} {order_time}", c="white on red")
            continue


if __name__ == "__main__":
    show_login_window()
